using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Xfinity.AI.Entities;

namespace Xfinity.Engine.SceneGraph
{
    /// <summary>
    /// A node containing a list of geometry and sub-nodes.
    /// </summary>
    [Serializable]
    public class SceneNode : IDrawable, IGameComponent
    {
        SceneNode[] nodes;
        BoundingBox bounds;
        List<PhysicalEntity> entities;

        /// <summary>
        /// Gets the entities.
        /// </summary>
        /// <value>The entities.</value>
        public List<PhysicalEntity> Entities
        {
            get { return entities; }
        }
        List<SceneNode> nonEmpty = new List<SceneNode>(8);
        /// <summary>
        /// Initializes a new instance of the <see cref="SceneNode"/> class.
        /// </summary>
        public SceneNode()
            : this(null)
        {

        }
        /// <summary>
        /// Initializes a new instance of the <see cref="SceneNode"/> class.
        /// </summary>
        /// <param name="models">The models.</param>
        public SceneNode(IEnumerable<PhysicalEntity> models)
        {
            if (models != null)
            {
                entities = new List<PhysicalEntity>(models);
            }
            else
            {
                entities = new List<PhysicalEntity>();
            }
        }

        /// <summary>
        /// Subdivides this node.
        /// </summary>
        public void Subdivide()
        {
            //check to see if Subdivide has already called, or if it's an empty node.
            if (nodes != null || nodes.Length ==0)
            {
                return;
            }

            if (entities.Count <= SceneGraph.MaxModelsPerNode)
            {
                CleanUpEmptyNodes();
                return;
            }
            nodes = new SceneNode[8];
            for (int i = 0; i < 8; i++)
            {
                nodes[i] = new SceneNode();
            }

            //get center of the node.
            Vector3 centerOfBounds = ((bounds.Max + bounds.Min) / 2);

            Vector3[] corners = bounds.GetCorners();

            for (int i = 0; i < 8; i++)
            {
                nodes[i].bounds = new BoundingBox(centerOfBounds, corners[i]);
            }

            //if a mesh is completely inside a node, add it to the node and remove it from the root.
            foreach (PhysicalEntity ent in entities)
            {
                foreach (ModelMesh mesh in ent.Model.Meshes)
                {
                    for (int i = 0; i < nodes.Length; i++)
                    {
                        if (nodes[i].bounds.Contains(mesh.BoundingSphere) == ContainmentType.Contains)
                        {
                            TransferOwnership(nodes[i], ent);
                            break;
                        }
                    }
                }
            }

            CleanUpEmptyNodes();

            if (nonEmpty.Count > 0)
            {
                foreach (SceneNode node in nonEmpty)
                {
                    node.Initialize();
                }
            }
        }

        private void CleanUpEmptyNodes()
        {
            //remove any nodes that are empty
            for (int i = 0; i < 8; i++)
            {
                if (nodes[i].entities.Count == 0)
                {
                    nodes[i] = null;
                }
                else
                {
                    nonEmpty.Add(nodes[i]);
                }
            }

            //trim excess fat
            nonEmpty.TrimExcess();
            if (nonEmpty.Count == 0)
            {
                nodes = null;
                nonEmpty = null;
            }
        }

        /// <summary>
        /// Draws the IDrawable.
        /// </summary>
        /// <param name="gameTime">Snapshot of the game's timing state.</param>
        public void Draw(GameTime gameTime)
        {
            GraphicsHelper.DrawClippedModel(entities);
            if (nonEmpty != null)
            {
                foreach (SceneNode node in nonEmpty)
                {
                    if (GraphicsHelper.ActiveCamera.Frustrum.Intersects(node.bounds))
                    {
                        node.Draw(gameTime);
                    }
                }
            }
        }

        /// <summary>
        /// Transfers the ownership.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="ent">The entity.</param>
        private void TransferOwnership(SceneNode node, PhysicalEntity ent)
        {
            node.entities.Add(ent);
            entities.Remove(ent);
        }

        #region IDrawable Members

        int drawOrder;
        bool visible;

        /// <summary>
        /// The order to draw this object in relative to other objects.  Object with a lower value are drawn first.
        /// </summary>
        /// <value></value>
        /// <returns>Order to draw this object in relative to other objects.</returns>
        public int DrawOrder
        {
            set
            {
                drawOrder = value;
                EventHandler temp = DrawOrderChanged;
                if (temp != null)
                {
                    temp(this, EventArgs.Empty);
                }
            }
            get { return drawOrder; }
        }

        /// <summary>
        /// Fired when the draw order changes.
        /// </summary>
        public event EventHandler DrawOrderChanged;

        /// <summary>
        /// Indicates whether IDrawable.Draw should be called in Game.Draw for this game component.
        /// </summary>
        /// <value></value>
        /// <returns>true if IDrawable.Draw should be called; false otherwise.
        /// </returns>
        public bool Visible
        {
            set
            {
                visible = value;
                EventHandler temp = VisibleChanged;
                if (temp != null)
                {
                    temp(this, EventArgs.Empty);
                }
            }
            get { return visible; }
        }
        /// <summary>
        /// Fires when the visibility changes.
        /// </summary>
        public event EventHandler VisibleChanged;

        #endregion

        #region IGameComponent Members

        /// <summary>
        /// Called when the component should be initialized.
        /// This method can be used for tasks like querying for services the component needs and setting up non-graphics resources.
        /// </summary>
        public void Initialize()
        {
            BoundingBox output = new BoundingBox(-Vector3.One, Vector3.One);

            if (entities != null)
            {
                BoundingBox local, result;
                foreach (PhysicalEntity entity in entities)
                {
                    foreach (ModelMesh mesh in entity.Model.Meshes)
                    {
                        local = BoundingBox.CreateFromSphere(mesh.BoundingSphere);
                        BoundingBox.CreateMerged(ref output, ref local, out result);
                        output = result;
                    }
                }
            }
            else
            {
                entities = new List<PhysicalEntity>();
            }

            bounds = output;
            if (entities.Count != 0)
            {
                Subdivide();
            }
        }
        #endregion
    }
}